<!DOCTYPE html>
<!--
Copyright (c) 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/base/base64.html">
<link rel="import" href="/tracing/extras/importer/etw/eventtrace_parser.html">
<link rel="import" href="/tracing/extras/importer/etw/process_parser.html">
<link rel="import" href="/tracing/extras/importer/etw/thread_parser.html">
<link rel="import" href="/tracing/importer/importer.html">
<link rel="import" href="/tracing/model/model.html">

<script>
/**
 * @fileoverview Imports JSON file with the raw payloads from a Windows event
 * trace into the Model. This format is outputted by Chrome running
 * on a Windows system.
 *
 * This importer assumes the events arrived as a JSON file and the payloads are
 * undecoded sequence of bytes in hex format string. The unit tests provide
 * examples of the trace format.
 *
 * The format of the system trace is
 *     {
 *       name: 'ETW',
 *       content: [ <events> ]
 *     }
  *
 * where the <events> are dictionary values with fields.
 *
 *     {
 *       guid: "1234-...",    // The unique GUID for the event.
 *       op: 12,              // The opcode of the event.
 *       ver: 1,              // The encoding version of the event.
 *       cpu: 0,              // The cpu id on which the event was captured.
 *       ts: 1092,            // The thread id on which the event was captured.
 *       payload: "aaaa"      // A base64 encoded string of the raw payload.
 *     }
 *
 * The payload is an undecoded version of the raw event sent by ETW.
 * This importer uses specific parsers to decode recognized events.
 * A parser need to register the recognized event by calling
 * registerEventHandler(guid, opcode, handler). The parser is responsible to
 * decode the payload and update the Model.
 *
 * The payload formats are described there:
 *   http://msdn.microsoft.com/en-us/library/windows/desktop/aa364085(v=vs.85).aspx
 *
 */
'use strict';

tr.exportTo('tr.e.importer.etw', function() {
  // GUID and opcode of a Thread DCStart event, as defined at the link above.
  const kThreadGuid = '3D6FA8D1-FE05-11D0-9DDA-00C04FD7BA7C';
  const kThreadDCStartOpcode = 3;

  /**
   * Represents the raw bytes payload decoder.
   * @constructor
   */
  function Decoder() {
    this.payload_ = new DataView(new ArrayBuffer(256));
  }

  Decoder.prototype = {
    __proto__: Object.prototype,

    reset(base64Payload) {
      const decodedSize = tr.b.Base64.getDecodedBufferLength(base64Payload);
      if (decodedSize > this.payload_.byteLength) {
        this.payload_ = new DataView(new ArrayBuffer(decodedSize));
      }

      tr.b.Base64.DecodeToTypedArray(base64Payload, this.payload_);
      this.position_ = 0;
    },

    skip(length) {
      this.position_ += length;
    },

    decodeUInt8() {
      const result = this.payload_.getUint8(this.position_, true);
      this.position_ += 1;
      return result;
    },

    decodeUInt16() {
      const result = this.payload_.getUint16(this.position_, true);
      this.position_ += 2;
      return result;
    },

    decodeUInt32() {
      const result = this.payload_.getUint32(this.position_, true);
      this.position_ += 4;
      return result;
    },

    decodeUInt64ToString() {
      // Javascript isn't able to manage 64-bit numeric values.
      const low = this.decodeUInt32();
      const high = this.decodeUInt32();
      const lowStr = ('0000000' + low.toString(16)).substr(-8);
      const highStr = ('0000000' + high.toString(16)).substr(-8);
      const result = highStr + lowStr;
      return result;
    },

    decodeInt8() {
      const result = this.payload_.getInt8(this.position_, true);
      this.position_ += 1;
      return result;
    },

    decodeInt16() {
      const result = this.payload_.getInt16(this.position_, true);
      this.position_ += 2;
      return result;
    },

    decodeInt32() {
      const result = this.payload_.getInt32(this.position_, true);
      this.position_ += 4;
      return result;
    },

    decodeInt64ToString() {
      // Javascript isn't able to manage 64-bit numeric values.
      // Fallback to unsigned 64-bit hexa value.
      return this.decodeUInt64ToString();
    },

    decodeUInteger(is64) {
      if (is64) {
        return this.decodeUInt64ToString();
      }
      return this.decodeUInt32();
    },

    decodeString() {
      let str = '';
      while (true) {
        const c = this.decodeUInt8();
        if (!c) return str;
        str = str + String.fromCharCode(c);
      }
    },

    decodeW16String() {
      let str = '';
      while (true) {
        const c = this.decodeUInt16();
        if (!c) return str;
        str = str + String.fromCharCode(c);
      }
    },

    decodeFixedW16String(length) {
      const oldPosition = this.position_;
      let str = '';
      for (let i = 0; i < length; i++) {
        const c = this.decodeUInt16();
        if (!c) break;
        str = str + String.fromCharCode(c);
      }

      // Move the position after the fixed buffer (i.e. wchar[length]).
      this.position_ = oldPosition + 2 * length;
      return str;
    },

    decodeBytes(length) {
      const bytes = [];
      for (let i = 0; i < length; ++i) {
        const c = this.decodeUInt8();
        bytes.push(c);
      }
      return bytes;
    },

    decodeSID(is64) {
      // Decode the TOKEN_USER structure.
      const pSid = this.decodeUInteger(is64);
      const attributes = this.decodeUInt32();

      // Skip padding.
      if (is64) {
        this.decodeUInt32();
      }

      // Decode the SID structure.
      const revision = this.decodeUInt8();
      const subAuthorityCount = this.decodeUInt8();
      this.decodeUInt16();
      this.decodeUInt32();

      if (revision !== 1) {
        throw new Error(
            'Invalid SID revision: could not decode the SID structure.');
      }

      const sid = this.decodeBytes(4 * subAuthorityCount);

      return {
        pSid,
        attributes,
        sid
      };
    },

    decodeSystemTime() {
      // Decode the SystemTime structure.
      const wYear = this.decodeInt16();
      const wMonth = this.decodeInt16();
      const wDayOfWeek = this.decodeInt16();
      const wDay = this.decodeInt16();
      const wHour = this.decodeInt16();
      const wMinute = this.decodeInt16();
      const wSecond = this.decodeInt16();
      const wMilliseconds = this.decodeInt16();
      return {
        wYear,
        wMonth,
        wDayOfWeek,
        wDay,
        wHour,
        wMinute,
        wSecond,
        wMilliseconds
      };
    },

    decodeTimeZoneInformation() {
      // Decode the TimeZoneInformation structure.
      const bias = this.decodeUInt32();
      const standardName = this.decodeFixedW16String(32);
      const standardDate = this.decodeSystemTime();
      const standardBias = this.decodeUInt32();
      const daylightName = this.decodeFixedW16String(32);
      const daylightDate = this.decodeSystemTime();
      const daylightBias = this.decodeUInt32();
      return {
        bias,
        standardName,
        standardDate,
        standardBias,
        daylightName,
        daylightDate,
        daylightBias
      };
    }

  };

  /**
   * Imports Windows ETW kernel events into a specified model.
   * @constructor
   */
  function EtwImporter(model, events) {
    this.importPriority = 3;
    this.model_ = model;
    this.events_ = events;
    this.handlers_ = {};
    this.decoder_ = new Decoder();
    this.walltime_ = undefined;
    this.ticks_ = undefined;
    this.is64bit_ = undefined;

    // A map of tids to their process pid. On Windows, the tid is global to
    // the system and doesn't need to belong to a process. As many events
    // only provide tid, this map allows to retrieve the parent process.
    this.tidsToPid_ = {};

    // Instantiate the parsers; this will register handlers for known events.
    const allTypeInfos = tr.e.importer.etw.Parser.getAllRegisteredTypeInfos();
    this.parsers_ = allTypeInfos.map(
        function(typeInfo) {
          return new typeInfo.constructor(this);
        }, this);
  }

  /**
   * Guesses whether the provided events is from a Windows ETW trace.
   * The object must has a property named 'name' with the value 'ETW' and
   * a property 'content' with all the undecoded events.
   *
   * @return {boolean} True when events is a Windows ETW array.
   */
  EtwImporter.canImport = function(events) {
    if (!events.hasOwnProperty('name') ||
        !events.hasOwnProperty('content') ||
        events.name !== 'ETW') {
      return false;
    }

    return true;
  };

  EtwImporter.prototype = {
    __proto__: tr.importer.Importer.prototype,

    get importerName() {
      return 'EtwImporter';
    },

    get model() {
      return this.model_;
    },

    createThreadIfNeeded(pid, tid) {
      this.tidsToPid_[tid] = pid;
    },

    removeThreadIfPresent(tid) {
      this.tidsToPid_[tid] = undefined;
    },

    getPidFromWindowsTid(tid) {
      if (tid === 0) return 0;
      const pid = this.tidsToPid_[tid];
      if (pid === undefined) {
        // Kernel threads are not defined.
        return 0;
      }
      return pid;
    },

    getThreadFromWindowsTid(tid) {
      const pid = this.getPidFromWindowsTid(tid);
      const process = this.model_.getProcess(pid);
      if (!process) return undefined;
      return process.getThread(tid);
    },

    /*
     * Retrieve the Cpu for a given cpuNumber.
     * @return {Cpu} A Cpu corresponding to the given cpuNumber.
     */
    getOrCreateCpu(cpuNumber) {
      const cpu = this.model_.kernel.getOrCreateCpu(cpuNumber);
      return cpu;
    },

    /**
     * Imports the data in this.events_ into this.model_.
     */
    importEvents() {
      this.events_.content.forEach(this.parseInfo.bind(this));

      if (this.walltime_ === undefined || this.ticks_ === undefined) {
        throw Error('Cannot find clock sync information in the system trace.');
      }

      if (this.is64bit_ === undefined) {
        /* crbug.com/604301 explains this message. */
        throw Error('Cannot determine pointer size of the system trace.' +
            'Consider deselecting "System tracing" or disabling the "Paging ' +
            'Executive" feature of Windows');
      }

      this.events_.content.forEach(this.parseEvent.bind(this));
    },

    importTimestamp(timestamp) {
      const ts = parseInt(timestamp, 16);
      return (ts - this.walltime_ + this.ticks_) / 1000.;
    },

    parseInfo(event) {
      // Retrieve clock sync information.
      if (event.hasOwnProperty('guid') &&
          event.hasOwnProperty('walltime') &&
          event.hasOwnProperty('tick') &&
          event.guid === 'ClockSync') {
        this.walltime_ = parseInt(event.walltime, 16);
        this.ticks_ = parseInt(event.tick, 16);
      }

      // Retrieve pointer size information from a Thread.DCStart event.
      if (this.is64bit_ === undefined &&
          event.hasOwnProperty('guid') &&
          event.hasOwnProperty('op') &&
          event.hasOwnProperty('ver') &&
          event.hasOwnProperty('payload') &&
          event.guid === kThreadGuid &&
          event.op === kThreadDCStartOpcode) {
        const decodedSize = tr.b.Base64.getDecodedBufferLength(event.payload);

        if (event.ver === 1) {
          if (decodedSize >= 52) {
            this.is64bit_ = true;
          } else {
            this.is64bit_ = false;
          }
        } else if (event.ver === 2) {
          if (decodedSize >= 64) {
            this.is64bit_ = true;
          } else {
            this.is64bit_ = false;
          }
        } else if (event.ver === 3) {
          if (decodedSize >= 60) {
            this.is64bit_ = true;
          } else {
            this.is64bit_ = false;
          }
        }
      }

      return true;
    },

    parseEvent(event) {
      if (!event.hasOwnProperty('guid') ||
          !event.hasOwnProperty('op') ||
          !event.hasOwnProperty('ver') ||
          !event.hasOwnProperty('cpu') ||
          !event.hasOwnProperty('ts') ||
          !event.hasOwnProperty('payload')) {
        return false;
      }

      const timestamp = this.importTimestamp(event.ts);

      // Create the event header.
      const header = {
        guid: event.guid,
        opcode: event.op,
        version: event.ver,
        cpu: event.cpu,
        timestamp,
        is64: this.is64bit_
      };

      // Set the payload to decode.
      const decoder = this.decoder_;
      decoder.reset(event.payload);

      // Retrieve the handler to decode the payload.
      const handler = this.getEventHandler(header.guid, header.opcode);
      if (!handler) return false;

      if (!handler(header, decoder)) {
        this.model_.importWarning({
          type: 'parse_error',
          message: 'Malformed ' + header.guid + ' event (' + event.payload + ')'
        });
        return false;
      }

      return true;
    },

    /**
     * Registers a windows ETW event handler used by parseEvent().
     */
    registerEventHandler(guid, opcode, handler) {
      if (this.handlers_[guid] === undefined) {
        this.handlers_[guid] = [];
      }
      this.handlers_[guid][opcode] = handler;
    },

    /**
     * Retrieves a registered event handler.
     */
    getEventHandler(guid, opcode) {
      if (this.handlers_[guid] === undefined) {
        return undefined;
      }
      return this.handlers_[guid][opcode];
    }

  };

  // Register the EtwImporter to the Importer.
  tr.importer.Importer.register(EtwImporter);

  return {
    EtwImporter,
  };
});
</script>
